﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SSharp.Core.DataTypes;

namespace SSharp.Core.Evaluator {
	/// <summary>
	/// Eval, apply and macros should be the core of the metacircular evaluator.
	/// </summary>
	public class Apply {
		/// <summary>
		/// Applies the procedure to the given arguments.
		/// </summary>
		public static EvaluationResult ApplyFunction(object func, object[] args) {
			// Use dynamic instead of switching over the type
			return ApplyDynamic((dynamic)func, args);
		}

		/// <summary>
		/// Application for primitive procedures - simply call the delegate
		/// </summary>
		private static EvaluationResult ApplyDynamic(PrimitiveProcedure func, object[] args) {
			return new EvaluationResult(func.Procedure(args));
		}

		/// <summary>
		/// Application for compound procedures - create a frame with the function's parameters and run the body
		/// </summary>
		private static EvaluationResult ApplyDynamic(CompoundProcedure func, object[] args) {
			IEnvironment env = ExtendEnvironment(func.Environment, func.Name, func.Parameters, args);
			return new MoreEvaluationNeeded(func.Body, env);
		}

		/// <summary>
		/// Called when no better match is fonud - throws an error because the func isn't a procedure.
		/// </summary>
		private static EvaluationResult ApplyDynamic(object func, object[] args) {
			throw new ApplicationError("Apply: expected a procedure; got " + func);
		}

		/// <summary>
		/// Extends the environment to contain the function parameters
		/// </summary>
		private static IEnvironment ExtendEnvironment(IEnvironment env, string procName, object argNames, object[] values) {
			Dictionary<Symbol, object> newVariables = new Dictionary<Symbol, object>();
			int index = 0;
			while (true) {
				// have we passed the last argument?
				if (argNames is List.EmptyList) {
					if (values.Length > index) {
						throw new ApplicationError(procName + ": too many arguments; expected " + index +  ", given " + values.Length);
					}
					break;
				}

				if (argNames is Cons) {
					// normal variable name
					if (values.Length <= index) {
						throw new ApplicationError(procName + ": not enough arguments; given " + values.Length);
					}
					Cons cons = (Cons)argNames;
					newVariables[(Symbol)cons.car] = values[index];

					argNames = cons.cdr;
					index++;
				} else {
					// argNames must be a Symbol - otherwise the Procedure definition is invalid.
					newVariables[(Symbol)argNames] = List.Create(values.Skip(index));
					break;
				}
			}
			return env.AddFrame(new Frame(newVariables));
		}

		/// <summary>
		/// Applies a macro by expanding it and evaluating the result.
		/// </summary>
		internal static EvaluationResult ApplyMacro(Macro macro, IList<object> args, IEnvironment env) {
			object expression = macro.Expand(args);
			return new MoreEvaluationNeeded(expression, env);
		}
	}
}
